﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace Xrm.AlmFramework.Console
{
    /// <summary>
    /// Analyses a CRM solution file to determine if there are any unpacked solution files on disk sthat are no longer contained
    /// within the solution.
    /// </summary>
    public class CrmSolutionAnalyser
    {

        #region ---------------------------------------- Constructor ------------------------------------------------

        private string solutionFilePath;
        private XmlNamespaceManager nsmgr;

        public CrmSolutionAnalyser(string filePath)
        {
            solutionFilePath = filePath;
        }

        #endregion

        #region ---------------------------------------- Properties -------------------------------------------------

        private System.Xml.XmlDocument _solutionXml;
        public System.Xml.XmlDocument SolutionXml
        {
            get
            {
                if (_solutionXml == null) Load();
                return _solutionXml;
            }
        }

        #endregion

        #region ---------------------------------------- Methods ----------------------------------------------------

        private void Load()
        {
            _solutionXml = new System.Xml.XmlDocument();
            _solutionXml.Load(this.solutionFilePath);

            // Set namespace manager
            nsmgr = new XmlNamespaceManager(_solutionXml.NameTable);
            nsmgr.AddNamespace("ns", @"http://www.w3.org/2001/XMLSchema-instance");
        }

        /// <summary>
        /// Compares the directory supplied to the Solution.xml file to determine if anything has been deleted from the solution
        /// and should therefore be deleted from the disk.
        /// 
        /// NB. This should probably be refactored so that the name is validated in conjunction with the RootComponent Type. 
        /// Currently this is only done for web resources so if 2 root components of different types share the same name 
        /// they won't be identified for deletion.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public List<Target> IdentifyDeletions(string path)
        {
            List<Target> retVal = new List<Target>();

            foreach (string rootFolder in System.IO.Directory.GetDirectories(path))
            {
                if (System.IO.Path.GetFileNameWithoutExtension(rootFolder).ToLower() != "other"
                    && System.IO.Path.GetFileNameWithoutExtension(rootFolder).ToLower() != "roles"
                     && System.IO.Path.GetFileNameWithoutExtension(rootFolder).ToLower() != "templates")
                {
                    // Iterate directories
                    foreach (string directory in System.IO.Directory.GetDirectories(rootFolder))
                    {
                        string name ="";
                        if (System.IO.Path.GetFileNameWithoutExtension(rootFolder).ToLower() == "PluginAssemblies".ToLower())
                        {
                            // 1. Check directory for files called *.data.xml
                            foreach (string file in System.IO.Directory.GetFiles(directory, "*.data.xml"))
                            {
                                name = ExtractNameFromDataXml(file, "PluginAssembly", "FullName");
                                break;
                            }
                        }
                        else
                        {
                            name = System.IO.Path.GetFileNameWithoutExtension(directory).ToLower();
                        }
                        if (SolutionXml.SelectSingleNode(@"//ImportExportXml/SolutionManifest/RootComponents/RootComponent[@schemaName='" + name + "']", nsmgr) == null
                                && SolutionXml.SelectSingleNode(@"//ImportExportXml/SolutionManifest/RootComponents/RootComponent[@id='" + name + "']", nsmgr) == null)
                        {
                            retVal.Add(new Target(Target.TargetType.Folder, directory));
                        }
                    }

                    // Iterate files
                    foreach (string file in System.IO.Directory.GetFiles(rootFolder))
                    {
                        string name;
                        if (System.IO.Path.GetFileNameWithoutExtension(rootFolder).ToLower() == "Workflows".ToLower())
                        {
                            if (file.EndsWith(".data.xml"))
                            {
                                name = ExtractNameFromDataXml(file, "Workflow", "WorkflowId");

                                if (SolutionXml.SelectSingleNode(@"//ImportExportXml/SolutionManifest/RootComponents/RootComponent[@schemaName='" + name + "']", nsmgr) == null
                                    && SolutionXml.SelectSingleNode(@"//ImportExportXml/SolutionManifest/RootComponents/RootComponent[@id='" + name + "']", nsmgr) == null)
                                {
                                    retVal.Add(new Target(Target.TargetType.File, file));
                                    //Determine if there is a corresponding .data.xml to remove as well
                                    string solFile = System.IO.Path.GetDirectoryName(file).Replace(".data.xml", "");
                                    if (System.IO.File.Exists(solFile))
                                    {
                                        retVal.Add(new Target(Target.TargetType.File, solFile));
                                    }
                                }
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else if (System.IO.Path.GetFileNameWithoutExtension(rootFolder).ToLower() == "WebResources".ToLower())
                        {
                            // Ignore .data.xml files for Web Resources
                            if (file.EndsWith(".data.xml"))
                            {
                                continue;
                            }
                            else
                            {
                                name = System.IO.Path.GetFileName(file).Replace(".xml", "");

                                if (SolutionXml.SelectSingleNode(@"//ImportExportXml/SolutionManifest/RootComponents/RootComponent[@schemaName='" + name + "' and @type='61']", nsmgr) == null
                                    && SolutionXml.SelectSingleNode(@"//ImportExportXml/SolutionManifest/RootComponents/RootComponent[@id='" + name + "' and @type='61']", nsmgr) == null)
                                {
                                    retVal.Add(new Target(Target.TargetType.File, file));
                                    //Determine if there is a corresponding .data.xml to remove as well
                                    string dataFile = System.IO.Path.GetDirectoryName(file) + @"\" + name + ".data.xml";
                                    if (System.IO.File.Exists(dataFile))
                                    {
                                        retVal.Add(new Target(Target.TargetType.File, dataFile));
                                    }
                                }
                            }
                        }
                        else
                        {
                            // Ignore specific files
                            if (file.EndsWith(".data.xml"))
                            {
                                continue;
                            }
                            else
                            {
                                name = System.IO.Path.GetFileName(file).Replace(".xml", "");

                                if (SolutionXml.SelectSingleNode(@"//ImportExportXml/SolutionManifest/RootComponents/RootComponent[@schemaName='" + name + "']", nsmgr) == null
                                    && SolutionXml.SelectSingleNode(@"//ImportExportXml/SolutionManifest/RootComponents/RootComponent[@id='" + name + "']", nsmgr) == null)
                                {
                                    retVal.Add(new Target(Target.TargetType.File, file));
                                    //Determine if there is a corresponding .data.xml to remove as well
                                    string dataFile = System.IO.Path.GetDirectoryName(file) + @"\" + name + ".data.xml";
                                    if (System.IO.File.Exists(dataFile))
                                    {
                                        retVal.Add(new Target(Target.TargetType.File, dataFile));
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return retVal;
        }

        public string ExtractNameFromDataXml(string file, string nodeName, string attributeName)
        {
            string name = null;

            // 2. Parse the first file found
            XmlDocument dataXml = new XmlDocument();
            dataXml.Load(file);

            XmlNode node = dataXml.SelectSingleNode(@"//" + nodeName);
            name = node.Attributes[attributeName].Value;
            return name;
        }


        #endregion

        public class Target
        {
            public enum TargetType
            {
                File,
                Folder
            }

            public string Path;
            public TargetType Type;

            public Target(TargetType type, string path)
            {
                this.Type = type;
                this.Path = path;
            }
        }
    }
}

/*
RootComponent Types
----------------------------------------------
1	Entity
2	Attribute
3	Relationship
4	Attribute Picklist Value
5	Attribute Lookup Value
6	View Attribute
7	Localized Label
8	Relationship Extra Condition
9	Option Set
10	Entity Relationship
11	Entity Relationship Role
12	Entity Relationship Relationships
13	Managed Property
20	Role
21	Role Privilege
22	Display String
23	Display String Map
24	Form
25	Organization
26	Saved Query
29	Workflow
31	Report
32	Report Entity
33	Report Category
34	Report Visibility
35	Attachment
36	Email Template
37	Contract Template
38	KB Article Template
39	Mail Merge Template
44	Duplicate Rule
45	Duplicate Rule Condition
46	Entity Map
47	Attribute Map
48	Ribbon Command
49	Ribbon Context Group
50	Ribbon Customization
52	Ribbon Rule
53	Ribbon Tab To Command Map
55	Ribbon Diff
59	Saved Query Visualization
60	System Form
61	Web Resource
62	Site Map
63	Connection Role
70	Field Security Profile
71	Field Permission
90	Plugin Type
91	Plugin Assembly
92	SDK Message Processing Step
93	SDK Message Processing Step Image
95	Service Endpoint
*/